#ifndef VECTOR4_HPP
#define VECTOR4_HPP

#include <iostream>
#include <iomanip>
#include <typeinfo>
#include <cmath>

#include "../NeonCore/Global.hpp"

namespace Neon
{
/*!
* Defines a Vector with 4 axis.
*/
class NEON_DLL Vector4
{
public:
	/*!
	Creates a Zero Vector4.
	*/
	Vector4();

	/*!
	Creates a custom Vector4.
	*\param x X axis value.
	*\param y Y axis value.
	*\param z Z axis value.
	*\param w W axis value.
	*/
	explicit Vector4(const nfloat x, const nfloat y, const nfloat z, const nfloat w);

	/*!
	Creates a Vector4 from another Vector4.
	*\param vec Source vector.
	*/
	Vector4(const Vector4 &vec);

	/*!
	Creates a Vector4 from another Vector4.
	*\param rhs Source vector.
	*\return The copied vector.
	*/
	Vector4& operator =(const Vector4 &rhs);

	/*!
	Inverts negative values to postive.
	*\return A postive vector.
	*/
	Vector4 operator +() const;

	/*!
	Adds two Vector4.
	*\param rhs Source vector.
	*\return Sum of two vectors.
	*/
	Vector4 operator +(const Vector4 &rhs) const;

	/*!
	Adds a Vector4 and a scalar.
	*\param rhs Vector scalar.
	*\return Sum of a vector and a scalar.
	*/
	Vector4 operator +(nfloat rhs) const;

	/*!
	Adds a scalar and a Vector4.
	*\param lhs Vector scalar.
	*\param rhs Source vector.
	*\return Sum of a scalar and a vector.
	*/
	friend Vector4 operator +(nfloat lhs, const Vector4 &rhs);

	/*!
	Adds two Vector4.
	*\param rhs Source vector.
	*\return Sum of two vectors.
	*/
	Vector4& operator +=(const Vector4 &rhs);

	/*!
	Adds a Vector4 and a scalar.
	*\param rhs Vector scalar.
	*\return Sum of a vector and a scalar.
	*/
	Vector4& operator +=(nfloat rhs);

	/*!
	Inverts postive values to negative.
	*\return A negative vector.
	*/
	Vector4 operator -() const;

	/*!
	Subtracts two vectors.
	*\param rhs Source vector.
	*\return Result of the subtraction.
	*/
	Vector4 operator -(const Vector4 &rhs) const;

	/*!
	Subtracts a Vector and a scalar.
	*\param rhs Vector scalar.
	*\return Result of the subtraction.
	*/
	Vector4 operator -(nfloat rhs) const;

	/*!
	Subtracts a scalar and a Vector.
	*\param lhs Vector scalar.
	*\param rhs Source vector.
	*\return Result of subtraction.
	*/
	friend Vector4 operator -(nfloat lhs, const Vector4 &rhs);

	/*!
	Subtracts two vectors.
	*\param rhs Source vector.
	*\return Result of the subtraction.
	*/
	Vector4& operator -=(const Vector4 &rhs);

	/*!
	Subtracts a Vector and a scalar.
	*\param rhs Vector scalar.
	*\return Result of the subtraction.
	*/
	Vector4& operator -=(nfloat rhs);

	/*!
	Multiplies two Vectors.
	*\param rhs Source vector.
	*\return Result of the multiplication.
	*/
	Vector4 operator *(const Vector4 &rhs) const;

	/*!
	Multiplies a Vector by a scalar.
	*\param rhs Vector scalar.
	*\return Result of the multiplication.
	*/
	Vector4 operator *(nfloat rhs) const;

	/*!
	Multiplies a scalar by a Vector.
	*\param lhs Vector scalar.
	*\param rhs Source vector.
	*\return Result of the multiplication.
	*/
	friend Vector4 operator *(nfloat lhs, const Vector4 &rhs);

	/*!
	Multiplies two Vectors.
	*\param rhs Source vector.
	*\return Result of the multiplication.
	*/
	Vector4& operator *=(const Vector4 &rhs);

	/*!
	Multiplies a Vector by a scalar.
	*\param rhs Vector scalar.
	*\return Result of the multiplication.
	*/
	Vector4& operator *=(nfloat rhs);

	/*!
	Divides two Vectors.
	*\param rhs Source vector.
	*\return Result of the Division.
	*/
	Vector4 operator /(const Vector4 &rhs) const;

	/*!
	Divides a Vector by a scalar.
	*\param rhs Vector scalar.
	*\return Result of the Division.
	*/
	Vector4 operator/(nfloat rhs) const;

	/*!
	Divides two Vectors.
	*\param rhs Source vector.
	*\return Result of the Division.
	*/
	Vector4& operator /=(const Vector4 &rhs);

	/*!
	Divides a Vector by a scalar.
	*\param rhs Vector scalar.
	*\return Result of the Division.
	*/
	Vector4& operator /=(nfloat rhs);

	/*!
	Tests two Vector4 for equality.
	*\param rhs Source vector.
	*\return true if the vectors are equal; false otherwise.
	*/
	bool operator ==(const Vector4 &rhs) const;

	/*!
	Tests two Vector4 for inequality.
	*\param rhs Source vector.
	*\return true if the vectors are not equal; false otherwise.
	*/
	bool operator !=(const Vector4 &rhs) const;

	/*!
	Prints a Vector4 to the console.
	*\param ostr Stream buffer.
	*\param vec Source vector.
	*\return A stream buffer of the vector.
	*/
	friend std::ostream& operator <<(std::ostream &ostr, const Vector4 &vec);

	/*!
	Returns a Vector4 containing the 2D Cartesian coordinates of a point specified in 2S barycentric (areal) coordinates.
	*\param vec1 Containing the 2D Cartesian coordinates of vertex 1 of the triangles.
	*\param vec2 Containing the 2D Cartesian coordinates of vertex 2 of the triangles.
	*\param vec3 Containing the 2D Cartesian coordinates of vertex 3 of the triangles.
	*\param amount1 Barycentric coordinate b2, which expresses the weighting factor towards vertex 2 (specified in vec2).
	*\param amount2 Barycentric coordinate b3, which expresses the weighting factor towards vertex 3 (specified in vec3).
	*\return Vector containing the 2D Cartesian coordinates of the specified point.
	*/
	NEON_STATIC Vector4 barycentric(const Vector4 &vec1, const Vector4 &vec2, const Vector4 &vec3, const nfloat amount1, const nfloat amount2);

	/*!
	Performs a Catmull-Rom interpolation using the specified positions.
	*\param vec1 The first position in the interpolation.
	*\param vec2 The second position in the interpolation.
	*\param vec3 The third position in the interpolation.
	*\param vec4 The fourth position in the interpolation.
	*\param amount Weighting factor.
	*\return Vector that is the result of the Catmull-Rom interpolation.
	*/
	NEON_STATIC Vector4 catmullRom(const Vector4 &vec1, const Vector4 &vec2, const Vector4 &vec3, const Vector4 &vec4, const nfloat amount);

	/*!
	Restricts a value to be within a specified range.
	*\param vec The vector to clamp.
	*\param min The minimum value.
	*\param max The maximum value.
	*\return The clamped value.
	*/
	NEON_STATIC Vector4 clamp(const Vector4 &vec, const Vector4 &min, const Vector4 &max);

	/*!
	Pointer for direct copy.
	*\return Pointer to the first index of the vector.
	*/
	NEON_INLINE const nfloat* data() const { return &X; }

	/*!
	Pointer for direct copy.
	*\return Pointer to the first index of the vector.
	*/
	NEON_INLINE nfloat* data() { return &X; }

	/*!
	Calculates the distance between two vectors.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\return Distance between the two vectors.
	*/
	NEON_STATIC nfloat distance(const Vector4 &vec1, const Vector4 &vec2);

	/*!
	Calculates the distance between two vectors.
	*\param vec Source vector.
	*\return Distance between this vector and the source vector.
	*/
	nfloat distance(const Vector4 &vec) const;

	/*!
	Calculates the distance between two vectors squared.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\return Distance between the source vectors squared.
	*/
	NEON_STATIC nfloat distanceSquared(const Vector4 &vec1, const Vector4 &vec2);

	/*!
	Calculates the distance between two vectors squared.
	*\param vec Source vector.
	*\return Distance between this vector and the source vector squared.
	*/
	nfloat distanceSquared(const Vector4 &vec) const;

	/*!
	Calculates the dot product of two vectors.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\return Dot Product of two vectors.
	*/
	NEON_STATIC nfloat dotProduct(const Vector4 &vec1, const Vector4 &vec2);

	/*!
	Calculates the dot product of two vectors.
	*\param vec Source vector.
	*\return Dot Product of this vector and the source vector.
	*/
	nfloat dotProduct(const Vector4 &vec) const;

	/*!
	Performs a Hermite spline interpolation.
	*\param vec1 Source position vector.
	*\param tang1 Source tangent vector.
	*\param vec2 Source position vector.
	*\param tang2 Source tangent vector.
	*\param amount Weighting factor.
	*\return Result of the Hermit spline interpolation.
	*/
	NEON_STATIC Vector4 hermite(const Vector4 &vec1, const Vector4 &tang1, const Vector4 &vec2, const Vector4 &tang2, const nfloat amount);

	/*!
	Calculates weither two given vectors are perpendicular, which form a 90 degrees angle.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\return true if the dot product of two vectors is 0; false otherwise.
	*/
	NEON_STATIC bool isPerpendicular(const Vector4 &vec1, const Vector4 &vec2);

	/*!
	Calculates weither two given vectors are perpendicular, which form a 90 degrees angle.
	*\param vec Source vector.
	*\return true if the dot product of this and the source vector is 0; false otherwise.
	*/
	bool isPerpendicular(const Vector4 &vec) const;

	/*!
	Calculates the length of the vector.
	*\param vec Source vector.
	*\return Length of the vector.
	*/
	NEON_STATIC nfloat length(const Vector4 &vec);

	/*!
	Calculates the length of the vector.
	*\return Length of the vector.
	*/
	nfloat length() const;

	/*!
	Calculates the length of the vector squared.
	*\return vec Source vector.
	*\return Length of the vector squared.
	*/
	NEON_STATIC nfloat lengthSquared(const Vector4 &vec);

	/*!
	Calculates the length of  the vector squared.
	*\return Length of the vector squared.
	*/
	nfloat lengthSquared() const;

	/*!
	Performs a linear interpolation between two vectors.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\param amount Value between 0 and 1 indicating the weight of vec2.
	*\return Linear interpolation of the two vectors.
	*/
	NEON_STATIC Vector4 lerp(const Vector4 &vec1, const Vector4 &vec2, const nfloat amount);

	/*!
	Returns a vector that contains the highest value from each matching pair of components.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\return The maximum vector.
	*/
	NEON_STATIC Vector4 max(const Vector4 &vec1, const Vector4 &vec2);

	/*!
	Returns a vector that contains the lowest value from each matching pair of components.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\return The minimum vector.
	*/
	NEON_STATIC Vector4 min(const Vector4 &vec1, const Vector4 &vec2);

	/*!
	Creates a unit vector from the specified vector.
	*\param vec Source vector.
	*\return Created unit vector.
	*/
	NEON_STATIC Vector4 normalise(const Vector4 &vec);

	/*!
	Turns the current vector into a unit vector.
	*/
	void normalise();

	/*!
	Returns a Vector4 with all of its components set to Zero.
	*\return A Vector4 with all its components set to Zero.
	*/
	NEON_STATIC Vector4 one;

	/*!
	Determines the reflect vector of the given vector and normal.
	*\param vec Source vector.
	*\param normal Normal of vector.
	*\return Resulting reflect vector.
	*/
	NEON_STATIC Vector4 reflect(const Vector4 &vec, const Vector4 &normal);

	/*!
	Interpolates between two values using a cubic equation.
	*\param vec1 Source vector.
	*\param vec2 Source vector.
	*\param amount Weighting value.
	*\return Interpolated value.
	*/
	NEON_STATIC Vector4 smoothStep(const Vector4 &vec1, const Vector4 &vec2, nfloat amount);

	/*!
	Returns the W unit Vector4 (0, 0, 0, 1).
	*\return The W unit Vector4.
	*/
	NEON_STATIC Vector4 wUnit;

	/*!
	Returns the X unit Vector4 (1, 0, 0, 0).
	*\return The X unit Vector4.
	*/
	NEON_STATIC Vector4 xUnit;

	/*!
	Returns the Y unit Vector4 (0, 1, 0, 0).
	*\return The Y unit Vector4.
	*/
	NEON_STATIC Vector4 yUnit;

	/*!
	Returns a Vector4 with all of its components set to Zero.
	*\return A Vector4 with all its components set to Zero.
	*/
	NEON_STATIC Vector4 zero;

	/*!
	Returns the Z unit Vector4 (0, 0, 1, 0).
	*\return The Z unit Vector4.
	*/
	NEON_STATIC Vector4 zUnit;

	/*!
	X coordinate.
	*/
	nfloat X;

	/*!
	Y coordinate.
	*/
	nfloat Y;

	/*!
	Z coordinate.
	*/
	nfloat Z;

	/*!
	W coordinate.
	*/
	nfloat W;
}; // End of class Vector4.

NEON_INLINE Vector4
operator +(nfloat lhs, const Vector4 &rhs)
{
	return Vector4(lhs + rhs.X, lhs + rhs.Y, lhs + rhs.Z, lhs + rhs.W);
}

NEON_INLINE Vector4
operator -(nfloat lhs, const Vector4 &rhs)
{
	return Vector4(lhs - rhs.X, lhs - rhs.Y, lhs - rhs.Z, lhs - rhs.W);
}

NEON_INLINE Vector4
operator *(nfloat lhs, const Vector4 &rhs)
{
	return Vector4(lhs * rhs.X, lhs * rhs.Y, lhs * rhs.Z, lhs * rhs.W);
}

NEON_INLINE std::ostream&
operator <<(std::ostream &ostr, const Vector4 &vec)
{
	return ostr << std::fixed << std::setprecision(4)
				<< "Vector [ x: " << vec.X << " y: " << vec.Y << " z: " << vec.Z << " w: " << vec.W << " ]" << std::endl;
}

} // End of namespace Neon.

#endif // VECTOR4_HPP
